package com.example.lotterysystem.service.impl;

import com.example.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.RedisUtil;
import com.example.lotterysystem.controller.param.CreateActivityParam;
import com.example.lotterysystem.controller.param.CreatePrizeByActivityParam;
import com.example.lotterysystem.controller.param.CreateUserByActivityParam;
import com.example.lotterysystem.controller.param.PageParam;
import com.example.lotterysystem.dao.dataobject.ActivityDO;
import com.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.example.lotterysystem.dao.dataobject.ActivityUserDO;
import com.example.lotterysystem.dao.dataobject.PrizeDO;
import com.example.lotterysystem.dao.mapper.*;
import com.example.lotterysystem.service.ActivityService;
import com.example.lotterysystem.service.dto.ActivityDTO;
import com.example.lotterysystem.service.dto.ActivityDetailDTO;
import com.example.lotterysystem.service.dto.CreateActivityDTO;
import com.example.lotterysystem.service.dto.PageListDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;


@Service
public class ActivityServiceImpl implements ActivityService {
    private static final Logger logger = LoggerFactory.getLogger(ActivityServiceImpl.class);


    /**
     *  为了区分业务, 约定活动编号前缀
     */
    private final String ACTIVITY_PREFIX = "ACTIVITY_";

    /**
     *  活动超时时间
     */
    private final Long  ACTIVITY_TIMEOUT = 60 * 60 * 24 * 3L ;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PrizeMapper prizeMapper;
    @Autowired
    private ActivityMapper activityMapper;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private ActivityUserMapper activityUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    @Transactional(rollbackFor = Exception.class) // 由于该方法涉及到多表，因此添加事务
    public CreateActivityDTO createActivity(CreateActivityParam param) {
        // 校验活动信息是否正确
        checkActivityInfo(param);

        // 保存活动信息
        ActivityDO activityDO = new ActivityDO();
        activityDO.setActivityName(param.getActivityName());
        activityDO.setDescription(param.getDescription());
        activityDO.setStatus(ActivityStatusEnum.RUNNING.name());
        activityMapper.insert(activityDO);

        //保存活动关联的奖品信息
        List<CreatePrizeByActivityParam> prizeParams = param.getActivityPrizeList();
        List<ActivityPrizeDO> activityPrizeDOList = prizeParams.stream()
                .map(prizeParam -> {
                    ActivityPrizeDO activityPrizeDO = new ActivityPrizeDO();
                    activityPrizeDO.setActivityId(activityDO.getId());
                    activityPrizeDO.setPrizeId(prizeParam.getPrizeId());
                    activityPrizeDO.setPrizeAmount(prizeParam.getPrizeAmount());
                    activityPrizeDO.setPrizeTiers(prizeParam.getPrizeTiers());
                    activityPrizeDO.setStatus(ActivityPrizeStatusEnum.INIT.name());
                    return activityPrizeDO;
                }).collect(Collectors.toList());
        activityPrizeMapper.batchInsert(activityPrizeDOList);

        // 保存活动关联的人员信息
        List<CreateUserByActivityParam> userParams = param.getActivityUserList();
        List<ActivityUserDO> activityUserDOList = userParams.stream()
                .map(userParam -> {
                    ActivityUserDO activityUserDO = new ActivityUserDO();
                    activityUserDO.setActivityId(activityDO.getId());
                    activityUserDO.setUserId(userParam.getUserId());
                    activityUserDO.setUserName(userParam.getUserName());
                    activityUserDO.setStatus(ActivityUserStatusEnum.INIT.name());
                    return activityUserDO;
                }).collect(Collectors.toList());
        activityUserMapper.batchInsert(activityUserDOList);
        // 整合完整的活动信息，存放 redis
        // ActivityDetailDTO 用于存放完整的活动信息（包括 活动数据、奖品数据、人员数据）
        // 使用 activityId 这个键，来找到响应的值（ActivityDetailDTO）

        // 先获取奖品基本属性表
        // 获取需要查询的奖品id
        List<Long> prizeIds =  param.getActivityPrizeList()
                .stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()
                .collect(Collectors.toList());
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);
        //
        ActivityDetailDTO detailDTO = convertToActivityDetailDTO(activityDO, activityUserDOList, prizeDOList, activityPrizeDOList);
        // 放入redis缓存
        cacheActivity(detailDTO);

        // 构造并返回活动Id
        CreateActivityDTO createActivityDTO = new CreateActivityDTO();
        createActivityDTO.setActivityId(activityDO.getId());
        return createActivityDTO;
    }

    /**
     * 根据分页参数查询活动列表
     * <p>
     * 此方法首先查询所有活动的总数，然后根据当前页码和页面大小获取当前页的活动列表
     * 它将查询结果转换为DTO列表，并返回一个包含总数和当前页DTO列表的PageListDTO对象
     *
     * @param param 分页参数，包含当前页码和每页大小
     * @return 返回一个PageListDTO对象，包含活动总数和当前页的活动DTO列表
     */
    @Override
    public PageListDTO<ActivityDTO> findActivityList(PageParam param) {
        // 查询活动总数
        int total = activityMapper.count();

        //获取当前页列表
        List<ActivityDO> activityDOList = activityMapper.selectActivityList(param.offset(), param.getPageSize());
        //将ActivityDO转换为ActivityDTO
        List<ActivityDTO> activityDTOList = activityDOList.stream()
                .map(activityDO -> {
                    ActivityDTO activityDTO = new ActivityDTO();
                    activityDTO.setActivityId(activityDO.getId());
                    activityDTO.setActivityName(activityDO.getActivityName());
                    activityDTO.setDescription(activityDO.getDescription());
                    activityDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));
                    return activityDTO;
                }).toList();

        //返回包含总数和当前页DTO列表的PageListDTO对象
        return new PageListDTO<>(total, activityDTOList);

    }
    /**
     * 根据活动Id获取活动详情
     * 此方法首先从缓存中获取活动详情，如果缓存中不存在，则从数据库中查询活动详情并缓存
     *
     * @param activityId 活动Id
     * @return 返回活动详情DTO
     */
    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {
        if (null == activityId) {
            logger.warn("查询活动详细信息失败，activityId为空！");
            return null;
        }
        // 查询 redis
        ActivityDetailDTO detailDTO = getActivityFromCache(activityId);
        if (null != detailDTO) {
            logger.info("查询活动详细信息成功！detailDTO={}",
                    JacksonUtil.writeValueAsString(detailDTO));
            return detailDTO;
        }

        // 如果redis不存在，查表
        // 活动表
        ActivityDO aDO = activityMapper.selectById(activityId);

        // 活动奖品表
        List<ActivityPrizeDO> apDOList =  activityPrizeMapper.selectByActivityId(activityId);

        // 活动人员表
        List<ActivityUserDO> auDOList = activityUserMapper.selectByActivityId(activityId);

        // 奖品表: 先获取要查询的奖品id
        List<Long> prizeIds = apDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .collect(Collectors.toList());
        List<PrizeDO> pDOList = prizeMapper.batchSelectByIds(prizeIds);
        // 整合活动详细信息，存放redis
        detailDTO = convertToActivityDetailDTO(aDO, auDOList, pDOList, apDOList);
        cacheActivity(detailDTO);
        // 返回
        return detailDTO;
    }

    /**
     * 根据活动id从缓存中获取活动详细信息
     *
     * @param activityId
     * @return
     */
    private ActivityDetailDTO getActivityFromCache(Long activityId) {
        if (null == activityId) {
            logger.warn("获取缓存活动数据的activityId为空！");
            return null;
        }
        try {
            String str = redisUtil.get(ACTIVITY_PREFIX + activityId);
            if (!StringUtils.hasText(str)) {
                logger.info("获取的缓存活动数据为空！key={}", ACTIVITY_PREFIX + activityId);
                return null;
            }
            return JacksonUtil.readValue(str, ActivityDetailDTO.class);
        } catch (Exception e) {
            logger.error("从缓存中获取活动信息异常，key={}", ACTIVITY_PREFIX + activityId, e);
            return null;
        }

    }
    /**
     * 缓存活动详情信息
     *
     * @param activityId
     */
    @Override
    public void cacheActivity(Long activityId) {
        if(null == activityId){
            logger.warn("缓存活动失败，activityId为空！");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_IS_EMPTY);
        }
        // 如果redis不存在，查表
        // 活动表
        ActivityDO aDO = activityMapper.selectById(activityId);
        if (null == aDO){
            logger.warn("缓存活动失败，activityId有误！");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_ERROR);
        }
        // 活动奖品表
        List<ActivityPrizeDO> apDOList =  activityPrizeMapper.selectByActivityId(activityId);

        // 活动人员表
        List<ActivityUserDO> auDOList = activityUserMapper.selectByActivityId(activityId);

        // 奖品表: 先获取要查询的奖品id
        List<Long> prizeIds = apDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .collect(Collectors.toList());
        List<PrizeDO> pDOList = prizeMapper.batchSelectByIds(prizeIds);
        // 整合活动详细信息，存放redis
        ActivityDetailDTO detailDTO = convertToActivityDetailDTO(aDO, auDOList, pDOList, apDOList);
        cacheActivity(detailDTO);
    }


    /**
     *  缓存完整的活动信息  ActivityDetailDTO
     * @param detailDTO
     */
    private void cacheActivity(ActivityDetailDTO detailDTO) {
        //
        if(null == detailDTO || null == detailDTO.getActivityId()){
            logger.warn("要缓存的信息不存在！");
            return;
        }
        try{
            redisUtil.set(ACTIVITY_PREFIX + detailDTO.getActivityId(),
                    JacksonUtil.writeValueAsString(detailDTO),ACTIVITY_TIMEOUT);
        }catch (Exception e){
            logger.error("缓存活动异常，ActivityServiceImpl ActivityDetailDTO={}",
                    JacksonUtil.writeValueAsString(detailDTO), e);

        }

    }
    /**
     * 将活动相关数据对象转换为活动详情传输对象
     * 此方法整合了活动基本信息、活动奖品信息和活动用户信息，构建了一个详细的活动详情DTO
     *
     * @param activityDO 活动数据对象，包含活动的基本信息
     * @param activityUserDOList 活动用户数据对象列表，包含参与活动的用户信息
     * @param prizeDOList 奖品数据对象列表，包含所有可能的奖品信息
     * @param activityPrizeDOList 活动奖品数据对象列表，包含活动中的奖品及其数量和状态
     * @return 返回一个包含完整活动详情的ActivityDetailDTO对象
     */
   private ActivityDetailDTO convertToActivityDetailDTO(
           ActivityDO activityDO,
           List<ActivityUserDO> activityUserDOList,
           List<PrizeDO> prizeDOList,
           List<ActivityPrizeDO> activityPrizeDOList) {
       // 构造完整的活动信息
       ActivityDetailDTO detailDTO = new ActivityDetailDTO();
       detailDTO.setActivityId(activityDO.getId());
       detailDTO.setActivityName(activityDO.getActivityName());
       detailDTO.setDesc(activityDO.getDescription());
       detailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));

       // apDO: {prizeId，amount, status}, {prizeId，amount, status}
       // pDO: {prizeid, name....},{prizeid, name....},{prizeid, name....}
       List<ActivityDetailDTO.PrizeDTO> prizeDTOList = activityPrizeDOList
               .stream()
               .map(apDO -> {
                   ActivityDetailDTO.PrizeDTO prizeDTO = new ActivityDetailDTO.PrizeDTO();
                   prizeDTO.setPrizeId(apDO.getPrizeId());
                   Optional<PrizeDO> optionalPrizeDO = prizeDOList.stream()
                           .filter(prizeDO -> prizeDO.getId().equals(apDO.getPrizeId()))
                           .findFirst();
                   //将第二行的每一个pdo和第一个的apdo根据奖品id进行比较，
                   //得到同id的第二行的第一个pdo
                   // 如果PrizeDO为空，不执行当前方法，不为空才执行
                   optionalPrizeDO.ifPresent(prizeDO -> {
                       prizeDTO.setName(prizeDO.getName());
                       prizeDTO.setImageUrl(prizeDO.getImageUrl());
                       prizeDTO.setPrice(prizeDO.getPrice());
                       prizeDTO.setDescription(prizeDO.getDescription());
                   });
                   prizeDTO.setTiers(ActivityPrizeTiersEnum.forName(apDO.getPrizeTiers()));
                   prizeDTO.setPrizeAmount(apDO.getPrizeAmount());
                   prizeDTO.setStatus(ActivityPrizeStatusEnum.forName(apDO.getStatus()));
                   return prizeDTO;
               }).collect(Collectors.toList());
       detailDTO.setPrizeDTOList(prizeDTOList);
       // 处理活动用户信息，将其转换为UserDTO列表
       List<ActivityDetailDTO.UserDTO> userDTOList = activityUserDOList.stream()
               .map(auDO -> {
                   ActivityDetailDTO.UserDTO userDTO = new ActivityDetailDTO.UserDTO();
                   userDTO.setUserId(auDO.getUserId());
                   userDTO.setUserName(auDO.getUserName());
                   userDTO.setStatus(ActivityUserStatusEnum.forName(auDO.getStatus()));
                   return userDTO;
               }).collect(Collectors.toList());
       detailDTO.setUserDTOList(userDTOList);
       return detailDTO;
   }
    /**
     * 检查活动信息的合法性
     * 尽管Controller层已经进行了非空判断，为了确保数据完整性，Service层再次进行校验
     * 主要校验活动参与人员和奖品信息的合法性，以及它们之间的匹配关系
     *
     * @param param 创建活动的参数对象，包含活动的详细信息
     * @throws ServiceException 如果活动信息不合法，则抛出服务异常
     */
    private void checkActivityInfo(CreateActivityParam param) {
        // 虽然Controller层已经进行非空判断，为了解偶 需要在service 层继续判断
        if(null == param ){
            throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_IS_EMPTY);
        }
        //校验维度一
        // 人员id在人员表中是否存在
        // 1 2 3  ->  1 2
        List<Long> userIds = param.getActivityUserList()
                .stream()
                .map(CreateUserByActivityParam::getUserId)
                .distinct()
                .collect(Collectors.toList());
        // 数据库中存在的人员id
        List<Long> existUserIds = userMapper.selectExistByIds(userIds);
        if (CollectionUtils.isEmpty(existUserIds)) {
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
        }
        userIds.forEach(id -> {
            if (!existUserIds.contains(id)) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_ERROR);
            }
        });

        // 奖品id在奖品表中是否存在
        // 奖品id在奖品表中是否存在
        List<Long> prizeIds = param.getActivityPrizeList()
                .stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()
                .collect(Collectors.toList());
        List<Long> existPrizeIds = prizeMapper.selectExistByIds(prizeIds);
        if (CollectionUtils.isEmpty(existPrizeIds)) {
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
        }
        prizeIds.forEach(id -> {
            if (!existPrizeIds.contains(id)) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_ERROR);
            }
        });

        // 人员数量大于等于奖品数量
        // 2个奖品 2 1
        int userAmount = param.getActivityUserList().size();
        long prizeAmount = param.getActivityPrizeList()
                .stream()
                .mapToLong(CreatePrizeByActivityParam::getPrizeAmount) // 2 1
                .sum();
        if (userAmount < prizeAmount) {
            throw new ServiceException(ServiceErrorCodeConstants.USER_PRIZE_AMOUNT_ERROR);
        }

        // 校验活动奖品等奖有效性
        param.getActivityPrizeList().forEach(prize -> {
            if (null == ActivityPrizeTiersEnum.forName(prize.getPrizeTiers())) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_TIERS_ERROR);
            }
        });
    }
}


